package com.eblacorp.archive.security.ldap.dao;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.naming.InvalidNameException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.ldap.LdapName;

import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.filter.LikeFilter;
import org.springframework.ldap.filter.OrFilter;

import com.eblacorp.archive.security.ldap.ContactAttributeMapper;
import com.eblacorp.archive.security.ldap.model.Contact;
import com.eblacorp.archive.security.ldap.model.Group;

/**
 * @author Mahmoud.Ezzat
 * 
 */
public class LDAPContactDao implements ContactDao {

	private LdapTemplate ldapTemplate;
	private static final String COMMON_NAME = "CN";
	private static final String SAM_NAME = "sAMAccountName";
	private static final String SEARCH_BASE = "OU=IT Admin,DC=PD,DC=LOCAL";

	private static final String DISTINGUISHED_NAME_ATTR_NAME = "distinguishedname";
	private static final String MEMBER_ATTR_NAME = "member";

	public void setLdapTemplate(LdapTemplate ldapTemplate) {
		this.ldapTemplate = ldapTemplate;
	}

	@Override
	public void create(Contact contact) throws InvalidNameException {
		LdapName distinguisedName = new LdapName(SEARCH_BASE);
		distinguisedName.add("cn=" + contact.getFullName());

		Attributes userAttributes = new BasicAttributes();
		userAttributes.put("sn", contact.getLastName());
		userAttributes.put("telephoneNumber", contact.getPhone());

		BasicAttribute classAttribute = new BasicAttribute("objectclass");
		classAttribute.add("top");
		classAttribute.add("person");
		userAttributes.put(classAttribute);

		ldapTemplate.bind(distinguisedName, null, userAttributes);
	}

	@Override
	public void delete(Contact contact) throws InvalidNameException {
		LdapName distinguisedName = new LdapName(SEARCH_BASE);
		distinguisedName.add("cn=" + contact.getFullName());
		ldapTemplate.unbind(distinguisedName);
	}

	@Override
	public List<String> listAllContactsNames() {
		List<String> names = new ArrayList<String>();
		for (Contact c : listContacts())
			names.add(c.getFullName() + c.getLastName());
		return names;
	}

	/**
	 * Return all contacts matching passed search term
	 * 
	 * @param sAMName
	 *            filter the contacts by this search term , SAMName is unique,
	 *            Only one Contact should be assigned to a SAM Name
	 * @return all matching contacts
	 */
	public Contact findContactBySAMName(String sAMName) {

		List<Contact> result = (List<Contact>) ldapTemplate.search(SEARCH_BASE, createSearchBySAMNameFilter(sAMName)
				.encode(), new ContactAttributeMapper());

		if (result != null && result.size() > 1) {
			throw new SecurityException("Invalid Search Result");
		}

		if (result == null || result.size() == 0) {
			return null;
		}

		return result.get(0);
	}

	@Override
	public List<Contact> searchContact(String searchTerm) {
		return (List<Contact>) ldapTemplate.search(SEARCH_BASE, createSearchContactFilter(searchTerm).encode(),
				new ContactAttributeMapper());
	}

	@Override
	public List<Contact> listContacts() {
		EqualsFilter filter = new EqualsFilter("objectclass", "person");
		return ldapTemplate.search(SEARCH_BASE, filter.encode(), new ContactAttributeMapper());
	}

	/**
	 * Create search contacts filter
	 * 
	 * @param mailNickName
	 *            filter the contacts by this search term
	 * @return Filter
	 */
	private Filter createSearchBySAMNameFilter(String mailNickName) {

		OrFilter searchContactsFilter = new OrFilter();

		searchContactsFilter.or(createPersonAndFilter(SAM_NAME, mailNickName));

		return searchContactsFilter;
	}

	/**
	 * Create search contacts filter
	 * 
	 * @param searchTerm
	 *            filter the contacts by this search term
	 * @return Filter
	 */
	private Filter createSearchContactFilter(String searchTerm) {

		OrFilter searchFilter = new OrFilter();
		searchFilter.or(createPersonAndFilter(createLikeFilter(SAM_NAME, searchTerm)));
		searchFilter.or(createPersonAndFilter(createLikeFilter(COMMON_NAME, searchTerm)));

		return searchFilter;
	}

	/**
	 * Create a filter with conditions: Contact is a person & 'filter'
	 * 
	 * @param filter
	 *            a filter
	 * @return Filter
	 */
	private Filter createPersonAndFilter(Filter filter) {

		AndFilter andFilter = new AndFilter();
		andFilter.and(new EqualsFilter("objectClass", "person"));
		andFilter.and(filter);

		return andFilter;
	}

	/**
	 * Create a filter with conditions: Contact is a person & attribute matches
	 * passed value
	 * 
	 * @param attribute
	 *            the attribute to put filter on
	 * @param value
	 *            the attribute value to put filter on
	 * @return Filter
	 */
	private Filter createPersonAndFilter(String attribute, String value) {

		AndFilter andFilter = new AndFilter();
		andFilter.and(new EqualsFilter("objectClass", "person"));
		andFilter.and(new EqualsFilter(attribute, value));

		return andFilter;
	}

	/**
	 * Create a Like Filter
	 * 
	 * @param attribute
	 *            the attribute to put filter on
	 * @param value
	 *            the attribute value to put filter on
	 * @return Filter
	 */
	private Filter createLikeFilter(String attribute, String value) {
		return new LikeFilter(attribute, "*" + value + "*");
	}

	@Override
	public void addContactToUOGroup(Contact contact, Group group) {
		try {
			DirContextAdapter dirContext = (DirContextAdapter) ldapTemplate.lookup(contact.getDn());
			String dnUserFull = dirContext.getStringAttribute(DISTINGUISHED_NAME_ATTR_NAME);
			DirContextOperations groupContextOperations = ldapTemplate.lookupContext(group.getDn());
			String[] currentMembers = groupContextOperations.getStringAttributes(MEMBER_ATTR_NAME);
			List<String> dnUserFullList = new ArrayList<String>();
			if (currentMembers != null && currentMembers.length > 0) {
				dnUserFullList.addAll(Arrays.asList(currentMembers));
			}
			dnUserFullList.add(dnUserFull);
			groupContextOperations.setAttributeValues(MEMBER_ATTR_NAME,
					dnUserFullList.toArray(new String[dnUserFullList.size()]));
			ldapTemplate.modifyAttributes(groupContextOperations);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void removeContactToUOGroup(Contact contact, Group group) {
		try {
			DirContextAdapter dirContext = (DirContextAdapter) ldapTemplate.lookup(contact.getDn());
			String dnUserFull = dirContext.getStringAttribute(DISTINGUISHED_NAME_ATTR_NAME);
			DirContextOperations groupContextOperations = ldapTemplate.lookupContext(group.getDn());
			String[] currentMembers = groupContextOperations.getStringAttributes(MEMBER_ATTR_NAME);
			List<String> dnUserFullList = new ArrayList<String>();
			if (currentMembers != null && currentMembers.length > 0) {
				dnUserFullList.addAll(Arrays.asList(currentMembers));
			}
			dnUserFullList.remove(dnUserFull);
			groupContextOperations.setAttributeValues(MEMBER_ATTR_NAME,
					dnUserFullList.toArray(new String[dnUserFullList.size()]));
			ldapTemplate.modifyAttributes(groupContextOperations);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}
